#ifndef __tenacitas_crosswords1_concept__organizer__h__
#define __tenacitas_crosswords1_concept__organizer__h__


/** @file */


/** @todo */

// ==> C++ Headers
#include <cstdint>

// ==> 3rds Headers

// ==> Our Headers
#include <tenacitas.return_code.core/rc.h>

#include "layouts.h"
#include "layout.h"
#include "coordinates.h"
#include "coordinate.h"
#include "word.h"

// ==> Namespaces
using namespace std;

// ==> Macro Constants

// ==> Macro Commands


/** ==> namespace of the organization */
namespace tenacitas {
    /** ==> namespace for the project */
    namespace crosswords {
        /** ==> bussiness, concept, displaying, interacting, transmitting,
            storing */ 
        namespace concept {

            // ==> Typedefs 

            // ==> Pre-Declarations

            // ==> Attributes 

            // ==> Functions

            // ==> Classes

            /** 
                @nosubgrouping 
            */
            /**
               @tparam supplier must define @p text and

               @tparam supplier::text is the type of string to be
               used: @p std::string, or @p std::wstring, or @p std::u16string, or
               @p std::u32string. 

               @nosubgrouping
            */
            template <typename supplier>
            class organizer {


            public:

                // ==> organizer - Public Internal Types

                /** */
                typedef typename supplier::text text;

                /** */
                typedef typename text::value_type letter;

                /** */
                typedef typename concept::word<supplier> word;

                /** */
                typedef typename word::category category;

                /** */
                typedef concept::layouts<supplier> layouts;
                
                /** */
                typedef typename layouts::layout_index layout_index;

                /** */
                typedef typename concept::layout<supplier> layout;

                /** */
                typedef typename layout::layout_id layout_id;

                /** */
                typedef typename layout::orientation orientation;

                /** */
                typedef typename concept::coordinates<supplier> coordinates;

                /** */
                typedef typename coordinates::index coordinate_index;

                /** */
                typedef concept::coordinate<supplier> coordinate;

                /** */
                typedef typename coordinate::position position;
                
                /** */
                enum constants : constant_id {
                    coordinate_frontier_colision = 100,
                        coordinate_value_unmatch = 101,
                        could_not_position = 102,
                        could_not_calculate_coords = 103,
                        no_layout_positioned = 104,
                        unable_to_create = 105,
                        unable_to_position = 106,
                        coordinate_occupied = 107};

                // ==> organizer - Friends

                /** Class allowed to access private members of @p organizer, in
                    order to perform tests */
                friend class organizer_tester;

                // ==> organizer - Public Constructors

                /** */
                inline organizer();

                /** */
                inline organizer(const organizer &) = delete;

                /** */
                inline organizer(organizer &&) = delete;

                /** Destructor */
                inline ~organizer();

                // ==> organizer - Public Accessors

                // ==> organizer - Public Helpers

                // ==> organizer - Public Processors

                // ==> organizer - Public Operators

                /** */
                rc_t operator()(layouts & p_layouts);

                /** */
                inline organizer & operator=(const organizer &) = delete;

                /** */
                inline organizer & operator=(organizer &&) = delete;

                /** not allowed */
                inline bool operator ==(const organizer & ) = delete;

                /** not allowed */
                inline bool operator !=(const organizer & ) = delete;

                /** not allowed */
                inline bool operator >=(const organizer & ) = delete;

                /** not allowed */
                inline bool operator >(const organizer & ) = delete;

                /** not allowed */
                inline bool operator <=(const organizer & ) = delete;

                /** not allowed */
                inline bool operator <(const organizer & ) = delete;

                /** not allowed */
                void* operator new (std::size_t size)
                    throw (std::bad_alloc) = delete; 

                /** not allowed */
                void operator delete (void *p_class_id) = delete;

                /** not allowed */
                void *operator new[] (size_t) = delete;

                /** not allowed */
                void operator delete[] (void *) = delete;

                // ==> organizer - Public Attributes

                /** */
                static const class_id id = 14321496649339ULL;
                

            private:

                // ==> organizer - Private Internal Types

                // ==> organizer - Private Constructors

                // ==> organizer - Private Accessors

                // ==> organizer - Private Helpers

                // ==> organizer - Private Processors

                /** */
                void unposition(layouts & p_layouts);

                /** */
                void position_first_layout(layouts & p_layouts);

                /** */
                rc_t position_layout(layouts & p_layouts, layout_index p_index);

                /** */
                rc<layout_index>
                find_last_positioned_layout(layouts & p_layouts) const; 

                /** */
                rc_t position_layout(layouts & p_layouts, 
                                     layout_index p_ref, 
                                     layout_index p_target);

                /** */
                rc<coordinates>
                coordinates_to_check(const layouts & p_layouts,
                                     const layout_index & p_target_layout,
                                     const coordinate_index & p_target_coord,
                                     const orientation & p_target_ori) const;

                /** */
                rc_t are_valid_coordinates(const layouts & p_layouts,
                                           layout_index p_target, 
                                           layout_index p_ref,
                                           const coordinates & p_coords) const;
                
                /** */
                rc_t is_valid_position(layouts & p_layouts,
                                       layout_index p_ref_lay,
                                       coordinate_index p_ref_coord,
                                       layout_index p_target_lay,
                                       coordinate_index p_target_coord,
                                       orientation p_target_ori) const;

                /** */
                void translate(layouts & p_layouts);
                
                // ==> organizer - Private Operators

                // ==> organizer - Private Attributes


            };
        }
    }
}
// =============================================================================
// I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::crosswords::concept;

// -----------------------------------------------------------------------------
template <typename supplier>
const class_id
organizer<supplier>::
id;

// -----------------------------------------------------------------------------
template <typename supplier>
organizer<supplier>::
organizer() {}

// -----------------------------------------------------------------------------
template <typename supplier>
organizer<supplier>::
~organizer() {}


// -----------------------------------------------------------------------------
template<typename supplier>
void
organizer<supplier>::
position_first_layout(layouts & p_layouts) {
    {
        static bool _first = true;
        if ( _first ) {  
            srand(time(NULL)); //seeding for the first time only!
            _first = false;
        }
    }

    p_layouts[0].set_position(0, 0,
                              ((rand() % 2) == 0) ?
                              orientation::hor : 
                              orientation::ver);
}


// -----------------------------------------------------------------------------
template<typename supplier>
rc<typename organizer<supplier>::layout_index>
organizer<supplier>::
find_last_positioned_layout(layouts & p_layouts) const {
    layout_index _i = p_layouts.get_last();
    while (true) {
        if (_i < 0) {
            return rc<layout_index>(id, constants::no_layout_positioned);
        }
        if (p_layouts[_i].is_positioned()) {
            break;
        }
        --_i;
    }
    return rc<layout_index>(std::move(_i));
}


// -----------------------------------------------------------------------------
template<typename supplier>
rc<typename organizer<supplier>::coordinates>
organizer<supplier>::
coordinates_to_check(const layouts & p_layouts,
                     const layout_index & p_target_layout,
                     const coordinate_index & p_target_coord,
                     const orientation & p_target_ori) const {

    coordinates _coords;
    const coordinates & _target_coordinates =
        p_layouts[p_target_layout].get_coordinates();

    std::cerr << "_target_coordinates = " << _target_coordinates << std::endl;
    
    coordinate_index _last_target_index = _target_coordinates.get_size() - 1;
    letter _l = ' ';

    if (p_target_ori == orientation::ver) {
        for (coordinate_index _i = 0; _i <= _last_target_index; _i++) {
            if ( // (_i != p_target_coord) && 
                 (_i != (p_target_coord - 1)) &&
                 (_i != (p_target_coord + 1)) ) {
                
                _coords.add(
                    coordinate(_target_coordinates[_i].get_line(),
                               _target_coordinates[_i].get_column(),
                               _l));
                _coords.add(
                    coordinate(_target_coordinates[_i].get_line(),
                               _target_coordinates[_i].get_column() + 1,
                               _l));
                _coords.add(
                    coordinate(_target_coordinates[_i].get_line(),
                               _target_coordinates[_i].get_column() - 1,
                               _l));
            }
        }

        const coordinate & _c0 = _target_coordinates[0];
            
        _coords.add(coordinate(_c0.get_line()-1,
                               _c0.get_column()-1,
                               _l));
        _coords.add(coordinate(_c0.get_line()-1,
                               _c0.get_column(),
                               _l));
        _coords.add(coordinate(_c0.get_line()-1,
                               _c0.get_column()+ 1,
                               _l));

        const coordinate & _cN = _target_coordinates[_last_target_index];
            
        _coords.add(coordinate(_cN.get_line()+1,
                               _cN.get_column()-1,
                               _l));
        _coords.add(coordinate(_cN.get_line()+1,
                               _cN.get_column(),
                               _l));
        _coords.add(coordinate(_cN.get_line()+1, 
                               _cN.get_column()+ 1,
                               _l));

    } else {
        for (coordinate_index _i = 0; _i <= _last_target_index; _i++) {
            if ( // (_i != p_target_coord) && 
                 (_i != (p_target_coord - 1)) &&
                 (_i != (p_target_coord + 1)) ) {
                
                _coords.add(
                    coordinate(_target_coordinates[_i].get_line(),
                               _target_coordinates[_i].get_column(),
                               _l));

                _coords.add(
                    coordinate(_target_coordinates[_i].get_line()-1,
                               _target_coordinates[_i].get_column(),
                               _l));
                _coords.add(
                    coordinate(_target_coordinates[_i].get_line(),
                               _target_coordinates[_i].get_column()+1,
                               _l));
            }
        }

        const coordinate & _c0 = _target_coordinates[0];
            
        _coords.add(coordinate(_c0.get_line()-1,
                               _c0.get_column()-1,
                               _l));
        _coords.add(coordinate(_c0.get_line(),
                               _c0.get_column()-1,
                               _l));
        _coords.add(coordinate(_c0.get_line()+1,
                               _c0.get_column()-1,
                               _l));

        const coordinate & _cN = _target_coordinates[_last_target_index];
            
        _coords.add(coordinate(_cN.get_line()-1,
                               _cN.get_column()+1,
                               _l));
        _coords.add(coordinate(_cN.get_line(),
                               _cN.get_column()+1,
                               _l));
        _coords.add(coordinate(_cN.get_line()+1,
                               _cN.get_column()+ 1,
                               _l));
    }
    std::cerr << _coords << std::endl;
    
    return rc<coordinates>(std::move(_coords));
}

// -----------------------------------------------------------------------------
template<typename supplier>
rc_t
organizer<supplier>::
are_valid_coordinates(const layouts & p_layouts,
                      layout_index p_target, 
                      layout_index p_ref,
                      const coordinates & p_coords) const {

    rc_t _rc;

    layout_id _target_id = p_layouts[p_target].get_id();
    layout_id _ref_id = p_layouts[p_ref].get_id();
    
    layout_index _size_layouts = p_layouts.get_size();
    coordinate_index _size_coordinates = p_coords.get_size();
    
    for (layout_index _i = 0; _i < _size_layouts; ++_i) {
        layout_id _id = p_layouts[_i].get_id();
        if ( (_id != _target_id) && (_id != _ref_id) ) {
            for (coordinate_index _p = 0; _p < _size_coordinates; ++_p) {
                if (p_layouts[_i].occupies(p_coords[_p])) {
                    return rc_t(id, constants::coordinate_occupied);
                }
            }
        }
    }
    return _rc;
}


// -----------------------------------------------------------------------------
template<typename supplier>
rc_t
organizer<supplier>::
is_valid_position(layouts & p_layouts,
                  layout_index p_ref_lay,
                  coordinate_index p_ref_coord,
                  layout_index p_target_lay,
                  coordinate_index p_target_coord,
                  orientation p_target_ori) const {

    // calculate coordinates that can not be occupied
    rc<coordinates> _rc_coords = coordinates_to_check(p_layouts,
                                                      p_target_lay,
                                                      p_target_coord,
                                                      p_target_ori);
    if (_rc_coords) {
        coordinates _coords = std::move(_rc_coords.get_obj());
        
        // check if any of them is occupied
        return are_valid_coordinates(p_layouts, p_target_lay,
                                     p_ref_lay, _coords);
    }
    return rc_t(id, constants::could_not_calculate_coords);    
}


// -----------------------------------------------------------------------------
template<typename supplier>
rc_t
organizer<supplier>::
position_layout(layouts & p_layouts,
                layout_index p_ref, layout_index p_target) {

    layout & _ref_layout = p_layouts[p_ref];
    coordinates & _ref_coords = _ref_layout.get_coordinates();
    coordinate_index _ref_last = _ref_coords.get_size();

    layout & _target_layout = p_layouts[p_target];
    coordinates & _target_coords = _target_layout.get_coordinates();
    coordinate_index _target_last = _target_coords.get_size();
    orientation _target_ori = (p_layouts[p_ref].get_orientation() ==
                               orientation::hor ?
                               orientation::ver :
                               orientation::hor);

    for (coordinate_index _ref_i = 0; _ref_i < _ref_last; ++_ref_i) {
        for (coordinate_index _target_i = 0;
             _target_i < _target_last;
             ++_target_i) {

            if (_ref_coords[_ref_i].get_letter() == 
                _target_coords[_target_i].get_letter()) {
                coordinate _c(_ref_coords[_ref_i]);
                if (_target_ori == orientation::ver) {
                    _c.set_line(_c.get_line() - _target_i);
                }
                else {
                    _c.set_column(_c.get_column() - _target_i); 
                }

                _target_layout.set_position(_c.get_line(),
                                            _c.get_column(),
                                            _target_ori); 

                if (is_valid_position(p_layouts, p_ref, _ref_i,
                                      p_target, _target_i, _target_ori)) {
                    return rc_t();
                }

                _target_layout.unposition();                

            }
        }
    }

    return rc_t(id, constants::could_not_position);

}


// -----------------------------------------------------------------------------
template<typename supplier>
rc_t
organizer<supplier>::
position_layout(layouts & p_layouts, layout_index p_target) {
    rc<layout_index> _rc_i = find_last_positioned_layout(p_layouts);
    if (!_rc_i) {
        return rc_t(_rc_i.get_class_id(), _rc_i.get_constant_id());
    }
    
    layout_index _last_positioned = _rc_i.get_obj();

    bool _stop = false;

    while(true) {
        if (_stop) {
            break;
        }

        if (_last_positioned == 0) {
            _stop = true;
        }
        
        if (position_layout(p_layouts, _last_positioned, p_target)) {
            return rc_t();
        }
        
        if (!_stop) {
            --_last_positioned;
        }
    }
    return rc_t(id, constants::unable_to_position);
}

// -----------------------------------------------------------------------------
template<typename supplier>
void
organizer<supplier>::
unposition(layouts & p_layouts) {
    layout_index _size = p_layouts.get_size();
    for (layout_index _i = 0; _i < _size; ++_i) {
        p_layouts[_i].unposition();
    }
}

// -----------------------------------------------------------------------------
template <typename supplier>
void
organizer<supplier>::
translate(layouts & p_layouts) {
    // find the most negative x and y coordinate
    position _line = 0;
    position _col = 0;
    
    layout_index _size_layouts = p_layouts.get_size();
    for (layout_index _lay_index = 0; _lay_index < _size_layouts; ++_lay_index) {
        const coordinates & _coords = p_layouts[_lay_index].get_coordinates();
        coordinate_index _size_coords = _coords.get_size();
        for (coordinate_index _coord_index = 0;
             _coord_index < _size_coords;
             ++_coord_index) {
            if (_coords[_coord_index].get_line() < _line) {
                _line = _coords[_coord_index].get_line();
            }
            if (_coords[_coord_index].get_column() < _col) {
                _col = _coords[_coord_index].get_column();
            }
        }
    }

    std::cerr << "line = " << _line << ", col = " << _col << endl;
    

    _line = -_line;
    _col = -_col;
    for (layout_index _lay_index = 0; _lay_index < _size_layouts; ++_lay_index) {
        coordinates & _coords = p_layouts[_lay_index].get_coordinates();
        coordinate_index _size_coords = _coords.get_size();
        for (coordinate_index _coord_index = 0;
             _coord_index < _size_coords;
             ++_coord_index) {
            _coords[_coord_index].set_line(_coords[_coord_index].get_line() +
                                           _line); 
            _coords[_coord_index].set_column(_coords[_coord_index].get_column() +
                                             _col); 
        }
    }
}
        

// -----------------------------------------------------------------------------
template <typename supplier>
rc_t
organizer<supplier>::
operator()(layouts & p_layouts) {

    p_layouts.sort();

    layout_id _original_first_layout = p_layouts[0].get_id();

    uint16_t _tries_counter = 0;

    rc_t _rc;
    
    while (true) {

        std::cerr << "attempt " << ++_tries_counter << std::endl;

        unposition(p_layouts);

        position_first_layout(p_layouts);

        // position the second layout relatively to the first, and so on,
        // orthogonally 
        layout_index _i (1);
    
        layout_index _e (p_layouts.get_size());

        while (_i < _e) {
            _rc = std::move(position_layout(p_layouts, _i));
            if (!_rc) {
                break;
            }
            ++_i;
        }

        if (_rc) {
            break;
        }

        p_layouts.move_first_to_last();
        
        if (p_layouts[0].get_id() == _original_first_layout) {
            // all the layouts were first
            _rc = rc_t(id, constants::unable_to_create);
            break;
        }
    }
    p_layouts.print(std::cerr);
    translate(p_layouts);
    return rc_t();
}



#endif
